package pso;

import java.io.File;
import java.io.FileWriter;
import java.util.Random;

public class ParticleSwarmOptimization {
	
	public PSOResult evaluateParticelSwarmOptimization(BlackBoxFunction function, Point lowerBound, Point upperBound
														,int swarmSize, PointGenerator startingPointGenerator
														,PointGenerator startingVelocityGenerator
														, BoundaryHandling boundaryHandling, int steps){
		return evaluateParticelSwarmOptimization(function, lowerBound, upperBound, swarmSize
													, startingPointGenerator, startingVelocityGenerator
													, boundaryHandling, steps, 0.728, 1.49, 1.49);
	}
	
	public PSOResult evaluateParticelSwarmOptimization(BlackBoxFunction function, Point lowerBound, Point upperBound
														, int swarmSize, PointGenerator startingPointGenerator
														, PointGenerator startingVelocityGenerator
														, BoundaryHandling boundaryHandling, int steps
														, double xhi, double c1, double c2){
		Particle[] particleSwarm = new Particle[swarmSize];
		Point globalOptimalPosition = null;
		double globalOptimalValue = 0.0;
		for(int i = 0; i < swarmSize; ++i){
			particleSwarm[i] = new Particle(startingPointGenerator.nextPoint(), startingVelocityGenerator.nextPoint(), function);
			if(i == 0){
				globalOptimalPosition = particleSwarm[i].getCurrentOptimalPosition();
				globalOptimalValue = particleSwarm[i].getCurrentOptimalValue();
			} else {
				if(particleSwarm[i].getCurrentOptimalValue() <= globalOptimalValue){
					globalOptimalValue = particleSwarm[i].getCurrentOptimalValue();
					globalOptimalPosition = particleSwarm[i].getCurrentOptimalPosition();
				}
			}
		}
		
		BoundaryHandler handler = null;
		switch(boundaryHandling){
		case BOUNCE:
			handler = new BoundaryHandlerBounce(lowerBound, upperBound);
			break;
		case RANDOM:
			handler = new BoundaryHandlerRandom(lowerBound, upperBound, startingPointGenerator, startingPointGenerator);
			break;
		case MODULO:
			handler = new BoundaryHandlerModulo(lowerBound, upperBound);
			break;
		case IGNORE:
			handler = new BoundaryHandlerIgnore();
			break;
		case STICKY_WALL:
			handler = new BoundaryHandlerStikyWall(lowerBound, upperBound);
			break;
		default:
			System.err.println("no Boundary Handling specified");
			return null;
		}
		Random random = new Random();
		int printFirstSteps = 30; // how many of the first swarms surele print
		double printEach = 0.1; //proportion of printed swarms
		int lastPrintedStep = 0;;
		for(int step = 0; true; ++step){
			if(step <= printFirstSteps || step == steps || step >= lastPrintedStep + printEach * steps){
				File file = new File("logs");
				if(file.isFile()){
					System.err.println("data generation not possible: file logs exists.");
				} else {
					if(!file.isDirectory()){
						file.mkdir();
					}
					this.log("logs/logIteration" + String.format("%08d", step), particleSwarm);
				}
				System.out.println("After " + step + "steps: "
						+ " global optimal found value: " + globalOptimalValue
						+ " corresponding position: " + globalOptimalPosition
						+ " used black box function calls: " + function.getNumEvalutions());
				lastPrintedStep = step;
			}
			if(step == steps)break;
			for(int i = 0; i < swarmSize; ++i){
				Particle cur = particleSwarm[i];
				Point oldVelocity = cur.getVelocity();
				Point localBestDirection = cur.getCurrentOptimalPosition().subtract(cur.getPosition());
				Point globalBestDirection = globalOptimalPosition.subtract(cur.getPosition());
				double r1 = random.nextDouble();
				double r2 = random.nextDouble();
				Point oldVelocityScaled = oldVelocity.scale(xhi);
				Point localBestDirectionScaled = localBestDirection.scale(c1).scale(r1);
				Point globalBestDirectionScaled = globalBestDirection.scale(c2).scale(r2);
				Point newVelocity = oldVelocityScaled
										.add(localBestDirectionScaled)
										.add(globalBestDirectionScaled);
				Point oldPosition = cur.getPosition();
				Point newPosition = oldPosition.add(newVelocity);
				cur.setVelocity(newVelocity);
				cur.setPosition(newPosition);
				handler.applyBoundaryHandling(cur);
				cur.evaluateFunctionOnParticle();
				if(cur.getCurrentOptimalValue() <= globalOptimalValue){
					globalOptimalValue = cur.getCurrentOptimalValue();
					globalOptimalPosition = cur.getCurrentOptimalPosition();
				}
			}
		}
		return new PSOResult(globalOptimalPosition, globalOptimalValue);
	}
	
	private void log(String filePath, Particle[] swarm){
		try{
			FileWriter w = new java.io.FileWriter(filePath);
			for(Particle p:swarm){
				w.append(p.getPosition().toString());
			}
			w.flush();
			w.close();
			
		}catch(Exception e){
			System.err.println(e.getMessage());
		}
		
		
		
	}
	
}
